﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Collections;

namespace PostProcessing
{
    public class Rule
    {
        #region Fields

        static StreamReader readerDados;
        public string antecedente;
        public string consequente;
        public float suporte;
        public float confianca;
        public float grauInteresse;

        List<string> antecedenteDesmembrado;

        public Rule generalizada;
        public Rule anteriorAGenEmLinha;

        #endregion Fields

        public List<string> AntecedenteDesmembrado
        {
            get
            {
                if (antecedenteDesmembrado == null)
                    desmembraAntecedente();

                return antecedenteDesmembrado;
            }
        }

        public Rule()
        {
            antecedente = null;
            consequente = null;
            suporte = 0;
            confianca = 0;
            grauInteresse = 0;

            antecedenteDesmembrado = null;
            generalizada = null;
        }
        public Rule(string antecedente, string consequente, float suporte, float confianca)
        {
            this.antecedente = antecedente;
            this.consequente = consequente;
            this.suporte = suporte;
            this.confianca = confianca;
        }

        /// <summary>
        /// Ordena o antecedente da regra.
        /// </summary>
        /// <param name="comparison">Método que faz a comparação entre strings.</param>
        public void ordenarAntecedente(Comparison<string> comparison)
        {
            antecedenteDesmembrado.Sort(comparison);
            antecedente = string.Join(" ", antecedenteDesmembrado.ToArray());
        }

        /// <summary>
        /// Aplica os valores atuais da regra para o field "anteriorAGenEmLinha".
        /// </summary>
        public void gerarAnterior()
        {
            anteriorAGenEmLinha = new Rule(antecedente, consequente, suporte, confianca);
        }

        /// <summary>
        /// Cria regra generalizada para esta regra caso ela já não exista, e altera um determinado item de seu antecedente.
        /// </summary>
        /// <param name="novoItemAntec"></param>
        /// <param name="indexAntec"></param>
        public void gerarGeneralizada(string novoItemAntec, int indexAntec, Log log)
        {
            bool addRegraAoLog = false;

            if (generalizada == null)
            {
                generalizada = new Rule(antecedente, consequente, -1, -1);

                addRegraAoLog = true;
            }

            generalizada.mudarItemAntec(novoItemAntec, indexAntec);

            if (addRegraAoLog)
                log.regrasGeneralizadas.Add(this.ToString(), generalizada.ToString());
        }

        /// <summary>
        /// Troca o valor de um item específico do antecedente.
        /// </summary>
        /// <param name="novoValor"></param>
        /// <param name="indexAntec"></param>
        public void mudarItemAntec(string novoValor, int indexAntec)
        {
            if (antecedenteDesmembrado == null)
                desmembraAntecedente();

            if (indexAntec < antecedenteDesmembrado.Count)  //POSSÍVEL PROBLEMA!!!!!!!!!!!!!!!!! 
            {
                antecedenteDesmembrado[indexAntec] = novoValor;

                antecedente = "";
                foreach (string item in antecedenteDesmembrado)
                {
                    antecedente += item + " ";
                }
                antecedente = antecedente.Remove(antecedente.Length - 1); //Remove o último espaço.
            }
        }

        /// <summary>
        /// Remove o valor de um item específico do antecedente.
        /// </summary>
        /// <param name="novoValor"></param>
        /// <param name="indexAntec"></param>
        public void removeItemAntec(int indexAntec)
        {
            if (antecedenteDesmembrado == null)
                desmembraAntecedente();

            if (indexAntec < antecedenteDesmembrado.Count)  //POSSÍVEL PROBLEMA!!!!!!!!!!!!!!!!!!!!!!!!!!
            {
                antecedenteDesmembrado.RemoveAt(indexAntec);
                antecedente = "";

                foreach (string item in antecedenteDesmembrado)
                {
                    antecedente += item + " ";
                }
                antecedente = antecedente.Remove(antecedente.Length - 1); //Remove o último espaço.
            }
        }

        /// <summary>
        /// Desmembra itens do antecedente para posterior processamento com taxonomias.
        /// </summary>
        private void desmembraAntecedente()
        {
            antecedenteDesmembrado = new List<string>();

            foreach (string item in antecedente.Split(new char[] { ' ' }))
            {
                if(item != "")
                    antecedenteDesmembrado.Add(item);
            }
        }

        /// <summary>
        /// Gera um objeto Rule à partir de uma regra em formato string.
        /// </summary>
        /// <param name="regra"></param>
        public static Rule geraRegra(string regra)
        {
            string ant;
            string cons;
            float sup = 0;
            float conf = 0;
            int indiceSeta = 0;
            int indiceParentesis = 0;
            int tamanhoAntecedente = 0;
            string supConf = "";
            bool segundaVez = false;

            indiceParentesis = 0;

            for (int i = 0; i < regra.Length; i++)
            {   
                //Descobre onde está a seta (<-) da regra.
                if (regra[i] == '<')
                {
                    indiceSeta = i;
                }

                //Descobre onde estão os valores de suporte e confiança.
                if (regra[i] == '(')
                {
                    indiceParentesis = i;
                    tamanhoAntecedente = i - (indiceSeta + 4);
                }

                //Obtém os valores suporte e confiança.
                if ((indiceParentesis > 0) && (i > indiceParentesis))
                {
                    if (regra[i] == '%')
                    {
                        if (segundaVez == false)
                        {
                            sup = float.Parse(supConf.Replace(".", ","));
                            segundaVez = true;
                        }
                        else
                        {
                            conf = float.Parse(supConf.Replace(".",","));
                        }
                        supConf = "";
                        i += 2; //Pula virgula e espaço.
                    }
                    else
                        supConf += regra[i];
                }
            }

            if (sup == 0 && conf == 0)
            {
                supConf = regra.Substring(indiceParentesis +1);
                supConf = supConf.Replace(")", "");
                supConf = supConf.Replace(",", ";");
                supConf = supConf.Replace(".", ",");
                sup = float.Parse(supConf.Split(new char[] { ';' })[0]);
                conf = float.Parse(supConf.Split(new char[] { ';' })[1]);
            }

            ant = regra.Substring(indiceSeta + 3, tamanhoAntecedente);
            cons = regra.Substring(0,indiceSeta - 1);

            return new Rule(ant, cons, sup, conf);
        }

        /// <summary>
        /// Desmembra uma determinada regra.
        /// </summary>
        /// <param name="regra"></param>
        /// <returns></returns>
        public static Rule desmembraRegra(string regra)
        {
            float grauInteresse;
            Rule rule;
            string[] regraSeparada;
            string regraSemGrau = "";
            regraSeparada = regra.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);

            regraSeparada[0] = regraSeparada[0].Replace("infinito", "Infinito");
            if (float.TryParse(regraSeparada[0], out grauInteresse))
            {
                regraSemGrau = string.Join(" ", regraSeparada, 1, regraSeparada.Length - 1);
                rule = geraRegra(regraSemGrau);
                rule.grauInteresse = grauInteresse;
            }
            else
                rule = geraRegra(regra);
            
            return rule;
        }

        /// <summary>
        /// Calcula suporte de uma regra.
        /// </summary>
        /// <param name="arqDados">Arquivo de Base de dados para o cálculo.</param>
        /// <param name="antecedente">Antecedente da regra.</param>
        /// <param name="consequente">Consequente da regra.</param>
        /// <returns>Valor de suporte.</returns>
        public static float calculaSuporteRegra(string nomeArqDados, string antecedente, string consequente)
        {
            int totalRegistros = 0;
            int qtdRegra = 0;
            string[] registro;
            char[] separador = new char[1] { ' ' };
            string[] antDecomposto;
            int contIgualdade = 0;
            bool achouConsequente = false;

            readerDados = new StreamReader(nomeArqDados);

            antDecomposto = antecedente.Split(separador, StringSplitOptions.RemoveEmptyEntries);

            while (!readerDados.EndOfStream)
            {
                registro = clearSpaces(readerDados.ReadLine().ToLower()).Split(separador);
                totalRegistros++;

                //Para cada item do registro...
                for (int i = 0; i < registro.Length; i++)
                {
                    //Verifica se algum dos itens do antecedente é igual ao item atual do registro.
                    for (int j = 0; j < antDecomposto.Length; j++)
                    {
                        if (registro[i].Equals(antDecomposto[j]))
                        {
                            contIgualdade++;
                        }
                    }
                    //Se o item atual do registro for igual ao consequente...
                    if (registro[i].Equals(consequente))
                    {
                        achouConsequente = true;
                    }
                }
                
                //Se todos os itens do antecedente e o consequente existem no registro...
                if ((contIgualdade == antDecomposto.Length) && achouConsequente)
                {
                    qtdRegra++;
                }
                contIgualdade = 0;
                achouConsequente = false;
            }

            readerDados.Close();
            return (float)qtdRegra / totalRegistros;
        }

        /// <summary>
        /// Calcula o suporte da regra, suporte do antecedente e suporte do consequente.
        /// </summary>
        /// <param name="dados"></param>
        /// <param name="antecedente"></param>
        /// <param name="consequente"></param>
        /// <returns></returns>
        public static List<float> calculaSuportes(string nomeArqDados, string antecedente, string consequente)
        {
            List<float> suportes = new List<float>(3);
            List<string> dados = new List<string>(100000);

            int totalRegistros = 0;
            int qtdRegra = 0;
            int qtdAntecedente = 0;
            int qtdConsequente = 0;
            string[] registro;
            char[] separador = new char[1] { ' ' };
            string[] antDecomposto;
            int contIgualdade = 0;
            bool achouConsequente = false;

            readerDados = new StreamReader(nomeArqDados);
            antDecomposto = antecedente.Split(separador, StringSplitOptions.RemoveEmptyEntries);

            while (!readerDados.EndOfStream)
            {
                //Lê 100000 registros da base de dados.
                for (int i = 0; i < 100000; i++)
                {
                    if (!readerDados.EndOfStream)
                        dados.Add(readerDados.ReadLine());
                    else
                        break;
                }

                //Processa os 100000 registros lidos.
                foreach (string regis in dados)
                {
                    registro = clearSpaces(regis.ToLower()).Split(separador);
                    totalRegistros++;

                    //Para cada item do registro...
                    for (int i = 0; i < registro.Length; i++)
                    {
                        //Verifica se algum dos itens do antecedente é igual ao item atual do registro.
                        for (int j = 0; j < antDecomposto.Length; j++)
                        {
                            if (registro[i].Equals(antDecomposto[j]))
                            {
                                contIgualdade++;
                            }
                        }
                        //Se o item atual do registro for igual ao consequente...
                        if (registro[i].Equals(consequente))
                        {
                            achouConsequente = true;
                            qtdConsequente++;
                        }
                    }

                    //Se todos os itens do antecedente e o consequente existem no registro...
                    if (contIgualdade == antDecomposto.Length)
                    {
                        if (achouConsequente)
                            qtdRegra++;

                        qtdAntecedente++;
                    }
                    contIgualdade = 0;
                    achouConsequente = false;
                }

                dados.Clear();
            }

            suportes.Add((float) qtdRegra / totalRegistros);    //Suporte da regra.
            suportes.Add((float) qtdAntecedente / totalRegistros);  //Suporte do antecedente.
            suportes.Add((float) qtdConsequente / totalRegistros);  //Suporte do consequente.

            return suportes;
        }

        /// <summary>
        /// Calcula suporte de uma regra.
        /// </summary>
        /// <param name="dados">Base de dados para o cálculo.</param>
        /// <param name="antecedente">Antecedente da regra.</param>
        /// <param name="consequente">Consequente da regra.</param>
        /// <returns>Valor de suporte.</returns>
        public static float calculaSuporteRegra(List<string> dados, string antecedente, string consequente)
        {
            int totalRegistros = 0;
            int qtdRegra = 0;
            string[] registro;
            char[] separador = new char[1] { ' ' };
            string[] antDecomposto;
            int contIgualdade = 0;
            bool achouConsequente = false;

            antDecomposto = antecedente.Split(separador, StringSplitOptions.RemoveEmptyEntries);

            foreach (string regis in dados)
            {
                registro = clearSpaces(regis.ToLower()).Split(separador);
                totalRegistros++;

                //Para cada item do registro...
                for (int i = 0; i < registro.Length; i++)
                {
                    //Verifica se algum dos itens do antecedente é igual ao item atual do registro.
                    for (int j = 0; j < antDecomposto.Length; j++)
                    {
                        if (registro[i].Equals(antDecomposto[j]))
                        {
                            contIgualdade++;
                        }
                    }
                    //Se o item atual do registro for igual ao consequente...
                    if (registro[i].Equals(consequente))
                    {
                        achouConsequente = true;
                    }
                }

                //Se todos os itens do antecedente e o consequente existem no registro...
                if ((contIgualdade == antDecomposto.Length) && achouConsequente)
                {
                    qtdRegra++;
                }
                contIgualdade = 0;
                achouConsequente = false;
            }

            return (float)qtdRegra / totalRegistros;
        }

        /// <summary>
        /// Calcula suporte do antecedente ou consequente.
        /// </summary>
        /// <param name="arqDados">Nome do arquivo de Base de dados para o cálculo.</param>
        /// <param name="antCons">Antecedente ou consequente da regra.</param>
        /// <returns>Valor de suporte.</returns>
        public static float calculaSuporteLado(string nomeArqDados, string antCons)
        {
            int totalRegistros = 0;
            int qtdAntCons = 0;
            string[] registro;
            char[] separador = new char[1]{' '};
            string[] antConsDecomposto;
            int contIgualdade = 0;

            readerDados = new StreamReader(nomeArqDados);

            antConsDecomposto = antCons.Split(separador, StringSplitOptions.RemoveEmptyEntries);

            while (!readerDados.EndOfStream)
            {
                registro = clearSpaces(readerDados.ReadLine().ToLower()).Split(separador);
                totalRegistros++;

                //Para cada item do registro...
                for (int i = 0; i < registro.Length; i++)
                {
                    //Verifica se algum dos itens do antecedente é igual ao item atual do registro.
                    for (int j = 0; j < antConsDecomposto.Length; j++)
                    {
                        if (registro[i].Equals(antConsDecomposto[j]))
                        {
                            contIgualdade++;
                        }
                    }
                }
                
                //Se todos os itens do antecedente existem no registro...
                if (contIgualdade == antConsDecomposto.Length)
                {
                    qtdAntCons++;
                }
                contIgualdade = 0;
            }

            readerDados.Close();
            return (float) qtdAntCons / totalRegistros;
        }

        /// <summary>
        /// Calcula suporte do antecedente ou consequente.
        /// </summary>
        /// <param name="dados">Base de dados para o cálculo.</param>
        /// <param name="antCons">Antecedente ou Consequente da regra.</param>
        /// <returns>Valor de suporte.</returns>
        public static float calculaSuporteLado(List<string> dados, string antCons)
        {
            int totalRegistros = 0;
            int qtdAntCons = 0;
            string[] registro;
            char[] separador = new char[1] { ' ' };
            string[] antConsDecomposto;
            int contIgualdade = 0;

            antConsDecomposto = antCons.Split(separador, StringSplitOptions.RemoveEmptyEntries);

            foreach (string regis in dados)
            {
                registro = clearSpaces(regis.ToLower()).Split(separador);
                totalRegistros++;

                //Para cada item do registro...
                for (int i = 0; i < registro.Length; i++)
                {
                    //Verifica se algum dos itens do antecedente é igual ao item atual do registro.
                    for (int j = 0; j < antConsDecomposto.Length; j++)
                    {
                        if (registro[i].Equals(antConsDecomposto[j]))
                        {
                            contIgualdade++;
                        }
                    }
                }

                //Se todos os itens do antecedente existem no registro...
                if (contIgualdade == antConsDecomposto.Length)
                {
                    qtdAntCons++;
                }
                contIgualdade = 0;
            }

            return (float)qtdAntCons / totalRegistros;
        }

        /// <summary>
        /// Compara igualdade entre duas regras
        /// </summary>
        /// <param name="primeiraRegra"></param>
        /// <param name="segundaRegra"></param>
        /// <returns></returns>
        public static bool comparaRegras(Rule primeiraRegra, Rule segundaRegra)
        {
            if (primeiraRegra.antecedente == segundaRegra.antecedente)
            {
                if (primeiraRegra.consequente == segundaRegra.consequente)
                {
                    return true;
                }
            }
            return false;
        }

        public override string ToString()
        {
            return consequente + " <- " + antecedente + " (" + suporte.ToString() + "%; " + confianca + "%)";
        }

        private static string clearSpaces(string linha)
        {
            string linhaLimpa = linha.Replace("  ", " ");

            if (linha != linhaLimpa)
                return clearSpaces(linhaLimpa);

            return linhaLimpa;
        }
    }
}
